using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Reflection;
using System.Diagnostics;
using System.Timers;

namespace FsObserver
{
    public class FsObserver
    {
        const int FILESYSTEMWATCHER_BUFFERSIZE = 32768; //tried: 16384, 4096; default: 4096
        
        private string filter = "*.*";
        private EventAction action = EventAction.Echo;
        private string actionParam = "";
        private bool subdirs = false;
        private FileInfo AppPathInfo = new FileInfo(Assembly.GetExecutingAssembly().Location);
        private string _WatchPath = System.Environment.CurrentDirectory;
        private string eventArgs = "FileName, Attributes, LastAccess, LastWrite, Security, Size, CreationTime, DirectoryName";
        private int _EventHandlerDelay = 5000;
        private List<AsyncEventArgs> EventQueue = new List<AsyncEventArgs>();
        private bool _Verbose;
        private string _ComparePath = "";
        private bool _LogErrorsToFile;
        private string _ConsoleOutCopyFile;
        private bool _ConsoleOutCopyFile_Append;
        private StreamWriter _ConsoleOutCopyFile_StreamWriter;
        private bool _CopyErrorToStdOut;
        private int _FswBufferSize = FILESYSTEMWATCHER_BUFFERSIZE;

        static void Main(string[] args)
        {
            var program = new FsObserver();
            program.RunCommandLineProgram(args);
        }

        public string StartMessage { get; set; }

        public int FswBufferSize
        {
            get { return _FswBufferSize; }
            set { _FswBufferSize = value; }
        }

        public string WatchPath
        {
            get { return _WatchPath; }
            set { _WatchPath = value; }
        }

        public bool IncludeSubdirectories
        {
            get { return subdirs; }
            set { subdirs = value; }
        }

        public string FileFilter
        {
            get { return filter; }
            set { filter = value; }
        }

        public int EventHandlerDelay
        {
            get { return _EventHandlerDelay; }
            set { _EventHandlerDelay = value; }
        }

        public bool Verbose
        {
            get { return _Verbose; }
            set { _Verbose = value; }
        }

        public string ComparePath
        {
            get { return _ComparePath; }
            set { _ComparePath = value; }
        }

        public bool LogErrorsToFile
        {
            get { return _LogErrorsToFile; }
            set { _LogErrorsToFile = value; }
        }

        public string ConsoleOutCopyFile
        {
            get { return _ConsoleOutCopyFile; }
            set { _ConsoleOutCopyFile = value; }
        }

        public bool ConsoleOutCopyFile_Append
        {
            get { return _ConsoleOutCopyFile_Append; }
            set { _ConsoleOutCopyFile_Append = value; }
        }

        public StreamWriter ConsoleOutCopyFile_StreamWriter
        {
            get { return _ConsoleOutCopyFile_StreamWriter; }
            set { _ConsoleOutCopyFile_StreamWriter = value; }
        }

        public bool CopyErrToStdOut
        {
            get { return _CopyErrorToStdOut; }
            set { _CopyErrorToStdOut = value; }
        }

        public EventAction Action
        {
            get { return action; }
            set { action = value; }
        }

        public string ActionParams
        {
            get { return actionParam; }
            set { actionParam = value; }
        }

        public NotifyFilters[] MonitoredEvents
        {
            get
            {
                var ret = new List<NotifyFilters>();
                var items = eventArgs.Split(',');
                foreach (var item in items)
                {
                    ret.Add((NotifyFilters)Enum.Parse(typeof(NotifyFilters), item.Trim()));
                }
                return ret.ToArray();
            }
            set
            {
                var sb = new StringBuilder();
                foreach (var item in value)
                {
                    if (sb.Length > 0) sb.Append(", ");
                    sb.Append(item.ToString());
                }
                eventArgs = sb.ToString();
            }
        }

        public void RunCommandLineProgram(params string[] args)
        {
            StartMessage = "Watching path: {0}";
            try
            {
                for (int a = 0; a < args.Length; a++)
                {
                    if (args[a].Trim().Replace("/", "-").StartsWith("-"))
                    {
                        switch (args[a].Trim().Replace("/", "-").ToLower())
                        {
                            case "-cstd":
                            case "-copystdout":
                                a++;
                                if (args.Length <= a)
                                {
                                    StdErrLine("Missing comparepath parameter.");
                                    ShowHelp();
                                    return;
                                }
                                if (args[a].Trim().ToLower() == "append")
                                {
                                    _ConsoleOutCopyFile_Append = true;
                                    a++;
                                }
                                _ConsoleOutCopyFile = args[a];
                                if (args.Length > a + 1 && args[a + 1].Trim().ToLower() == "append")
                                {
                                    _ConsoleOutCopyFile_Append = true;
                                    a++;
                                }
                                break;
                            case "-cerrstd":
                            case "-copyerrstdout":
                                _CopyErrorToStdOut = true;
                                break;
                            case "-lef":
                            case "-logerrorstofile":
                                _LogErrorsToFile = true;
                                break;
                            case "-cp":
                            case "-comparepath":
                                a++;
                                if (args.Length <= a)
                                {
                                    StdErrLine("Missing comparepath parameter.");
                                    ShowHelp();
                                    return;
                                }
                                _ComparePath = args[a];
                                break;
                            case "-v":
                            case "-verbose":
                                _Verbose = true;
                                break;
                            case "-d":
                            case "-delay":
                                a++;
                                if (args.Length <= a)
                                {
                                    StdErrLine("Missing delay parameter.");
                                    //ShowHelp();
                                    return;
                                }
                                _EventHandlerDelay = int.Parse(args[a]);
                                break;
                            case "-path":
                            case "-p":
                                a++;
                                if (args.Length <= a)
                                {
                                    StdErrLine("Missing path parameter.");
                                    //ShowHelp();
                                    return;
                                }
                                _WatchPath = args[a];
                                break;
                            case "-filter":
                            case "-f":
                                a++;
                                if (args.Length <= a)
                                {
                                    StdErrLine("Missing filter parameter.");
                                    //ShowHelp();
                                    return;
                                }
                                filter = args[a];
                                break;
                            case "-subdirs":
                            case "-s":
                                subdirs = true;
                                break;
                            case "-e":
                            case "-event":
                                a++;
                                if (args.Length <= a)
                                {
                                    StdErrLine("Missing event parameter.");
                                    //ShowHelp();
                                    return;
                                }
                                eventArgs = args[a];
                                break;
                            case "-action":
                            case "-a":
                                a++;
                                if (args.Length <= a)
                                {
                                    StdErrLine("Missing action parameter.");
                                    //ShowHelp();
                                    return;
                                }
                                try
                                {
                                    action = (EventAction)Enum.Parse(
                                        typeof(EventAction), args[a], true);
                                    switch (action.ToString().ToLower())
                                    {
                                        case "command":
                                            a++;
                                            if (args.Length <= a)
                                            {
                                                StdErrLine("Missing action command parameter.");
                                                //ShowHelp();
                                                return;
                                            }
                                            actionParam = args[a];
                                            break;
                                        case "copyvscompare":
                                        case "copy":
                                            a++;
                                            if (args.Length <= a)
                                            {
                                                StdErrLine("Missing action copy parameter.");
                                                //ShowHelp();
                                                return;
                                            }
                                            actionParam = args[a];
                                            if (!Directory.Exists(actionParam))
                                            {
                                                StdErrLine("Error: " + actionParam + " does not exist.");
                                            }
                                            break;
                                        case "echo":
                                            break;
                                        default:
                                            ShowHelp();
                                            return;
                                    }
                                }
                                catch (Exception e)
                                {
                                    StdOutLine(e.Message);
                                    ShowHelp();
                                    return;
                                }
                                break;
                            case "-?":
                            case "-help":
                            case "--help":
                                ShowHelp();
                                return;
                            default:
                                //ShowHelp();
                                StdErrLine("Unknown flag: " + args[a] + "\r\n"
                                    + "Use -? or -help to view options.");
                                return;
                        }
                    }
                }
                if (action == EventAction.CopyVsCompare && !Directory.Exists(_ComparePath))
                {
                    StdErrLine("Error: No ComparePath value specified.\r\n"
                        + "To use CopyVsCompare, you must set the -cp flag.\r\n"
                        + "Use -? for usage information.");
                    return;
                }
            }
            catch (Exception e)
            {
                StdOutLine(e.Message);
                if (e.InnerException != null)
                    StdOutLine("Inner Exception: " + e.InnerException.Message);
#if DEBUG
                StdOutLine(e.StackTrace);
                if (System.Diagnostics.Debugger.IsAttached) throw e;
#else
                ShowHelp();
#endif
            }
#if (!DEBUG)
                try
                {
#endif
            StartWatcher();
#if (!DEBUG)
                }
                catch (Exception e2)
                {
                    StdOutLine(e2.Message);
                    if (e2.InnerException != null)
                        StdOutLine("Inner Exception: " + e2.InnerException.Message);
                    LogError(e2);
                }
#endif
        }

        private void StdOutLine(string line)
        {
            Console.WriteLine(line);
            if (_ConsoleOutCopyFile != null && _ConsoleOutCopyFile != "")
            {
                SetupStdOutFile();
                _ConsoleOutCopyFile_StreamWriter.WriteLine(line);
            }
        }

        private void StdOut(string text)
        {
            Console.Write(text);
            if (_ConsoleOutCopyFile != null && _ConsoleOutCopyFile != "")
            {
                SetupStdOutFile();
                _ConsoleOutCopyFile_StreamWriter.Write(text);
            }
        }

        private void StdErrLine(string line) 
        {
            Console.Error.WriteLine(line);
            if (_CopyErrorToStdOut) StdOutLine(line);
        }

        private void StdErr(string text)
        {
            Console.Error.Write(text);
            if (_CopyErrorToStdOut) StdOut(text);
        }

        private void SetupStdOutFile()
        {
            if (_ConsoleOutCopyFile_StreamWriter == null)
            {
                bool append = ConsoleOutCopyFile_Append;
                if (File.Exists(_ConsoleOutCopyFile))
                {
                    if (!append) File.Delete(_ConsoleOutCopyFile);
                }
                _ConsoleOutCopyFile_StreamWriter = new StreamWriter(_ConsoleOutCopyFile, append);
                _ConsoleOutCopyFile_StreamWriter.AutoFlush = true;
            }
        }

        private void ShowHelp()
        {
            StringBuilder helpText = new StringBuilder();
            helpText.AppendLine();
            helpText.AppendLine(AppPathInfo.Name 
                + "  [-p[ath] {path}]"
                + "  [-f[ilter] {filter}]"
                + "  [-s[ubdir] {subdir}]"
                + "  [-e[vent] \"{LastAccess, DirectoryName, etc}\"]"
                + "  [-a[ction] {action {actionParam}}]"
                + "  [-d[elay] {ms}]"
                + "  [-cp/-comparepath {path}]"
                + "  [-v[erbose]]");
            helpText.AppendLine();
            helpText.AppendLine("The FsObserver command monitors file system events and performs an action given");
            helpText.AppendLine("your execution parameters such as logging or mirroring.");
            helpText.AppendLine();
            helpText.AppendLine(" -h or -help\tShows this text.");
            helpText.AppendLine();
            helpText.AppendLine(" -p or -path\tSpecify the path to watch, i.e. \"C:\\\". Use \"\".");
            helpText.AppendLine("\t\tThe default watch path is current directory.");
            helpText.AppendLine();
            helpText.AppendLine(" -f or -filter\tSpecify the file filter, i.e. \"*.txt\".");
            helpText.AppendLine("\t\tThe default filter is *.* (all files).");
            helpText.AppendLine();
            helpText.AppendLine(" -s or -subdir\tSpecify to extend watch to subdirectories of path.");
            helpText.AppendLine();
            helpText.AppendLine(" -e or -event\tSpecify events to watch for: FileName, Attributes, LastAccess,");
            helpText.AppendLine("\t\tLastWrite, Security, Size, CreationTime, DirectoryName. Use \"\".");
            helpText.AppendLine();
            helpText.AppendLine(" -d or -delay\tSpecify the number of milliseconds to delay the event handler.");
            helpText.AppendLine("\t\tThe default delay is 5000 ms.");
            helpText.AppendLine();
            helpText.AppendLine(" -cp or -comparepath");
            helpText.AppendLine();
            helpText.AppendLine("\t\tSpecify to set the compare path property. This is only\n"
                + "\t\tused with \"-action copyvscompare\". If the observed\n"
                + "\t\tfile also exists in the compare path, no action will\n"
                + "\t\toccur.");
            helpText.AppendLine();
            helpText.AppendLine(" -v or -verbose\tSpecify to describe the event and how it is handled\n"
                + "\t\tin greater detail.");
            helpText.AppendLine();
            helpText.AppendLine(" -a or -action\tSpecify the action to invoke when a file is touched");
            helpText.AppendLine("\t\t(modified, added, or deleted).");
            helpText.AppendLine();
            helpText.AppendLine("\t\tAction Options:");
            helpText.AppendLine();
            helpText.AppendLine("\t\t-action echo");
            helpText.AppendLine();
            helpText.AppendLine("\t\t\tDisplays the touched file(s) in the console.");
            helpText.AppendLine();
            /*
                    .Replace("%1", "\"" + path + "\"")
                    .Replace("%2", eventType)
                    .Replace("%3", "\"" + file + "\"")
                    .Replace("%4", "\"" + dfile + "\"")
                    .Replace("%5", "\"" + dpath + "\""));
             */
            helpText.AppendLine("\t\t-action command \"{command} %1\"");
            helpText.AppendLine();
            helpText.AppendLine("\t\t\tExecutes a command, optionally using \"%1\" as the\n"
                + "\t\t\tobserved file path, %2 as the observed event types\n"
                + "\t\t\t(the -e flag), %3 as the filename, %4 as the verbose\n"
                + "\t\t\tfilename descriptor, and %5 as the verbose file path\n"
                + "\t\t\tdescriptor.\n"
                + "\t\t\tUse quotation marks (\"\") around the whole -action\n\t\t\tparameter.");
            helpText.AppendLine();
            helpText.AppendLine("\t\t-action copy \"{target directory}\"");
            helpText.AppendLine();
            helpText.AppendLine("\t\t\tCopies the touched file(s) to the specified target");
            helpText.AppendLine("\t\t\tdirectory, respecting the virtual directory hierarchy.");
            helpText.AppendLine("\t\t\tSurround this parameter with quotation marks (\"\").");
            helpText.AppendLine();
            helpText.AppendLine("\t\t-action copyvscompare \"{target directory}\"");
            helpText.AppendLine();
            helpText.AppendLine("\t\t\tCopies the touched file(s) to the specified target ");
            helpText.AppendLine("\t\t\tdirectory, respecting the virtual directory hierarchy.");
            helpText.AppendLine("\t\t\tThe file copy is not performed if the file(s)\n"
                + "\t\t\texist in the path specified by the -cp flag.");
            helpText.AppendLine();
            helpText.AppendLine("Example:");
            helpText.AppendLine();
            helpText.AppendLine(" " + AppPathInfo.Name + " -p \"" + Environment.CurrentDirectory + "\""
                + " -f \"*.txt\"");
            helpText.AppendLine(" -s"
                + " -e \"FileName, LastWrite\""
                + " -a \"echo\""
                + " -d 1000");
            helpText.AppendLine();
            helpText.AppendLine("This example will monitor the current directory and its subdirectories");
            helpText.AppendLine("for renames and edits to .txt files and will echo the change after a");
            helpText.AppendLine("delay of 1000 milliseconds.");
            StdOut(helpText.ToString());
        }

        public void StartWatcher()
        {
            StdOutLine(string.Format(StartMessage ?? string.Empty, new object[] { WatchPath, FileFilter, eventArgs, Action, ActionParams, Verbose }));

            using (FileSystemWatcher fsw = SetupFileSystemWatcher())
            {
                if (fsw == null) return;

                int reattemptItem = 0;
                while (true)
                {
                    // sleep long enough to allow event handlers 
                    // to catch up with the operating system
                    System.Threading.Thread.Sleep(100);
                    for (int i = 0; i < EventQueue.Count && i >= 0; i++)
                    {
                        if (DateTime.Now.Ticks - EventQueue[i].TimeStamp.Ticks
                                >= EventQueue[i].Delay * TimeSpan.TicksPerMillisecond)
                        {
                            bool succeeded = false;
                            try
                            {
                                EventQueue[i].Handler.DynamicInvoke(EventQueue[i].Args);
                                succeeded = true;
                            }
                            catch (System.IO.IOException e) 
                            {
                                StdErrLine(e.Message + " (Item will remain in queue to re-attempt.)");
                                reattemptItem++;
                            }

                            if (succeeded || reattemptItem > 10)
                            {
                                if (reattemptItem > 10)
                                {
                                    StdErrLine("Item in queue continued to fail after 10 attempts. Removing from queue.");
                                    reattemptItem = 0;
                                }
                                EventQueue.RemoveAt(i);
                                i--;
                            }
                        }
                    }
                }
            }
        }

        private FileSystemWatcher SetupFileSystemWatcher()
        {
            FileSystemWatcher fsw = new FileSystemWatcher();
            fsw.Path = _WatchPath;
            fsw.Filter = filter;
            fsw.InternalBufferSize = _FswBufferSize;
            fsw.IncludeSubdirectories = subdirs;
            if (eventArgs != null && eventArgs.Trim() != "")
            {
                string[] events = eventArgs.Split(',');
                for (int i = 0; i < events.Length; i++)
                {
                    try
                    {
                        string e = events[i].Trim();
                        if (i == 0)
                        {
                            fsw.NotifyFilter =
                                (NotifyFilters)Enum.Parse(
                                typeof(NotifyFilters), e, true);
                        }
                        else
                        {
                            fsw.NotifyFilter |=
                                (NotifyFilters)Enum.Parse(
                                typeof(NotifyFilters), e, true);
                        }
                    }
                    catch (Exception e)
                    {
                        StdErrLine("Error while parsing [-event] parameter:\n"
                            + e.Message);
                        return null;
                    }
                }
            }
            else
            {
                fsw.NotifyFilter = NotifyFilters.LastWrite;
            }

            fsw.Changed += new FileSystemEventHandler(FileSystemWatcher_Changed);
            fsw.Created += new FileSystemEventHandler(FileSystemWatcher_Created);
            fsw.Deleted += new FileSystemEventHandler(FileSystemWatcher_Deleted);
            fsw.Renamed += new RenamedEventHandler(FileSystemWatcher_Renamed);
            fsw.Error += new ErrorEventHandler(FileSystemWatcher_Error);

            fsw.EnableRaisingEvents = true;

            return fsw;
        }

        public enum EventAction
        {
            /// <summary>
            /// Displays the touched file in the console.
            /// </summary>
            Echo,
            /// <summary>
            /// Executes a shell command.
            /// </summary>
            Command,
            /// <summary>
            /// Copies the file to the specified target directory, respecting the virtual directory hierarchy.
            /// </summary>
            Copy,
            /// <summary>
            /// Copies the file to the specified target directory, respecting the virtual directory hierarchy.  
            /// The file copy is not performed if the file exists in the path specified by the ComparePath property.
            /// </summary>
            CopyVsCompare
        }

        delegate void IOEventHandler(string eventType, string file, string path, EventArgs e);

        void HandleEvent(string eventType, string file, string path, EventArgs e)
        {
            string dfile = file;
            string dpath = "at " + path.Substring(_WatchPath.Length);
            bool isFolder = Directory.Exists(path);
            var file_or_folder = isFolder ? "Folder" : "File";

            if (eventType.ToLower() == "renamed")
            {
                dfile = ((RenamedEventArgs)e).OldName
                    + " > " + ((RenamedEventArgs)e).Name;
                dpath = ((RenamedEventArgs)e).OldFullPath.Substring(_WatchPath.Length)
                    + " > " + ((RenamedEventArgs)e).FullPath.Substring(_WatchPath.Length);
            }
            if (_Verbose)
            {
                StdOutLine("\r\n"
                    + file_or_folder + " " 
                    + eventType.ToLower() + "\t" + dfile + "\r\n"
                    + "\t(" + dpath + ")");
            }
            else StdOutLine(file_or_folder + " " + eventType.ToLower() + "\t" + dpath);
            switch (action)
            {
                case EventAction.Echo:
                    break;
                case EventAction.Copy:
                case EventAction.CopyVsCompare:
                    if (!Directory.Exists(actionParam))
                    {
                        StdErrLine("Error: " + actionParam + " does not exist!");
                        return;
                    }
                    string targetPath = actionParam;
                    string targetComparePath = _ComparePath;
                    switch (eventType.ToLower())
                    {
                        case "error":
                            // do nothing
                            break;
                        case "deleted":
                            string oldCopyPath = targetPath + ((FileSystemEventArgs)e)
                                .FullPath.Substring(_WatchPath.Length);
                            string oldComparePath = _ComparePath + ((FileSystemEventArgs)e)
                                .FullPath.Substring(_WatchPath.Length);
                            if (File.Exists(oldCopyPath))
                            {
                                if (action != EventAction.CopyVsCompare || !File.Exists(oldComparePath))
                                {
                                    if (_Verbose)
                                        StdOutLine("Copy at " + oldCopyPath + " exists. Deleting it ...");
                                    File.Delete(oldCopyPath);
                                }
                            }
                            else if (Directory.Exists(oldCopyPath))
                            {
                                if (action != EventAction.CopyVsCompare || !Directory.Exists(oldComparePath))
                                {
                                    if (_Verbose)
                                        StdOutLine("Copy at " + oldCopyPath + " exists. Deleting it ...");
                                    Directory.Delete(oldCopyPath, true);
                                }
                            }
                            break;
                        case "renamed":
                            // rename at copy target
                            oldCopyPath = targetPath + ((RenamedEventArgs)e)
                                .OldFullPath.Substring(_WatchPath.Length);
                            oldComparePath = _ComparePath + ((RenamedEventArgs)e)
                                .OldFullPath.Substring(_WatchPath.Length);
                            targetPath += ((RenamedEventArgs)e)
                                .FullPath.Substring(_WatchPath.Length);
                            targetComparePath += ((RenamedEventArgs)e)
                                .FullPath.Substring(_WatchPath.Length);
                            if (File.Exists(oldCopyPath))
                            {
                                if (action != EventAction.CopyVsCompare || (
                                    !File.Exists(oldComparePath) &&
                                    !File.Exists(targetComparePath)))
                                {
                                    if (_Verbose)
                                        StdOutLine("Copy at " + oldCopyPath + " exists. Renaming it to " + targetPath + ".");
                                    File.Move(oldCopyPath, targetPath);
                                }
                                else
                                {
                                    StdOutLine("IGNORED: File exists in " + _ComparePath);
                                }
                            }
                            else if (Directory.Exists(oldCopyPath))
                            {
                                if (action != EventAction.CopyVsCompare || (
                                    !Directory.Exists(oldComparePath) &&
                                    !Directory.Exists(targetComparePath)))
                                {
                                    if (_Verbose)
                                        StdOutLine("Copy at " + oldCopyPath + " exists. Renaming it to " + targetPath + ".");
                                    Directory.Move(oldCopyPath, targetPath);
                                }
                                else
                                {
                                    StdOutLine("IGNORED: Directory exists in " + _ComparePath);
                                }
                            }
                            break;
                        case "created":
                        case "changed":
                            targetPath = path.Substring(_WatchPath.Length);
                            if (actionParam.EndsWith("\\")) actionParam = actionParam.Substring(0, actionParam.Length - 1);
                            if (!targetPath.StartsWith("\\")) targetPath = "\\" + targetPath;
                            targetPath = actionParam + targetPath;

                            targetComparePath = _ComparePath + targetPath.Substring(actionParam.Length);

                            if (action == EventAction.CopyVsCompare && (
                                File.Exists(targetComparePath) || 
                                Directory.Exists(targetComparePath)))
                            {
                                StdOutLine("IGNORED: File or directory exists in " + _ComparePath);
                                break;
                            }

                            BuildFileDirectory(targetPath);
                            if (_Verbose)
                                StdOutLine("Copying " + path + "\n\t to " + targetPath);
                            if (File.Exists(targetPath))
                            {
                                if (_Verbose)
                                    StdOutLine("File exists at " + targetPath + ". Overwriting...");
                            }
                            else if (Directory.Exists(targetPath))
                            {
                            }
                            if (File.Exists(path)) {
                                File.Copy(path, targetPath, true);
                            } else if (Directory.Exists(path)) {
                                if (eventType.ToLower() == "created")
                                {
                                    if (!Directory.Exists(targetPath))
                                    {
                                        Directory.CreateDirectory(path);
                                    }
                                }
                            }
                            break;
                    }
                    break;
                case EventAction.Command:
                    string[] startArgs = actionParam.Split(' ');
                    ProcessStartInfo psi;
                    //if (startArgs.Length > 1)
                    //{
                    //    psi = new ProcessStartInfo(startArgs[0],
                    //        actionParam.Substring(startArgs[0].Length).Trim());
                    //}
                    //else psi = new ProcessStartInfo(actionParam.Replace("%1", "^\"" + path + "^\""));
                    ConsoleColor origColor = Console.ForegroundColor;
                    Console.ForegroundColor = ConsoleColor.DarkYellow;
                    psi = new ProcessStartInfo(
                        Environment.GetEnvironmentVariable("ComSpec"),
                        "/C " + actionParam
                        .Replace("%1", "\"" + path + "\"")
                        .Replace("%2", eventType)
                        .Replace("%3", "\"" + file + "\"")
                        .Replace("%4", "\"" + dfile + "\"")
                        .Replace("%5", "\"" + dpath + "\""));
                    psi.UseShellExecute = false;
                    Process p = Process.Start(psi);
                    p.WaitForExit();
                    Console.ForegroundColor = origColor;
                    break;
            }
        }

        void BuildFileDirectory(string filepath)
        {
            if (File.Exists(filepath)) return;
            string dirpath = filepath.Substring(0,
                filepath.LastIndexOf("\\"));
            if (Directory.Exists(dirpath)) return;
            BuildDirectory(dirpath);
        }

        void BuildDirectory(string dirpath)
        {
            if (Directory.Exists(dirpath)) return;
            string[] dirs = dirpath.Split('\\');
            for (int i = 0; i < dirs.Length; i++)
            {
                string bdir = "";
                for (int s = 0; s <= i; s++)
                {
                    if (!Directory.Exists(bdir) && !string.IsNullOrEmpty(bdir) && bdir != "\\" && bdir != "\\\\")
                    {
                        if (bdir.StartsWith("\\\\") &&
                            bdir.LastIndexOf("\\") == bdir.IndexOf("\\", 2))
                        {
                            // skip
                        }
                        else
                        {
                            if (_Verbose) StdOutLine("Creating directory " + bdir);
                            Directory.CreateDirectory(bdir);
                        }
                    }
                    bdir += dirs[s] + "\\";
                }
            }
            if (!Directory.Exists(dirpath))
            {
                if (_Verbose) StdOutLine("Creating directory " + dirpath);
                Directory.CreateDirectory(dirpath);
            }
        }

        void FileSystemWatcher_Error(object sender, ErrorEventArgs e)
        {
            StdErrLine("An error occurred: " + e.GetException().Message);
        }

        void FileSystemWatcher_Renamed(object sender, RenamedEventArgs e)
        {
            IOEventHandler handler = new IOEventHandler(HandleEvent);

            AsyncEventArgs aea = new AsyncEventArgs(
                DateTime.Now, handler, _EventHandlerDelay, "Renamed", e.Name, e.FullPath, e);
            EventQueue.Add(aea);
            //HandleEvent("Renamed", e.Name, e.FullPath, e);
        }

        void FileSystemWatcher_Deleted(object sender, FileSystemEventArgs e)
        {
            IOEventHandler handler = new IOEventHandler(HandleEvent);

            AsyncEventArgs aea = new AsyncEventArgs(
                DateTime.Now, handler, _EventHandlerDelay, "Deleted", e.Name, e.FullPath, e);
            EventQueue.Add(aea);
            //HandleEvent("Deleted", e.Name, e.FullPath, e);
        }

        void FileSystemWatcher_Created(object sender, FileSystemEventArgs e)
        {
            IOEventHandler handler = new IOEventHandler(HandleEvent);

            AsyncEventArgs aea = new AsyncEventArgs(
                DateTime.Now, handler, _EventHandlerDelay, "Created", e.Name, e.FullPath, e);
            EventQueue.Add(aea);
            //HandleEvent("Created", e.Name, e.FullPath, e);
        }

        void FileSystemWatcher_Changed(object sender, FileSystemEventArgs e)
        {
            IOEventHandler handler = new IOEventHandler(HandleEvent);

            AsyncEventArgs aea = new AsyncEventArgs(
                DateTime.Now, handler, _EventHandlerDelay, "Changed", e.Name, e.FullPath, e);
            EventQueue.Add(aea);
            //HandleEvent("Changed", e.Name, e.FullPath, e);
        }

        void LogError(Exception e)
        {
            if (_LogErrorsToFile)
            {
                string logfile = AppPathInfo.FullName
                    .Substring(0, AppPathInfo.FullName.LastIndexOf("."))
                    + "_errors.log";
                File.AppendAllText(logfile,
                    DateTime.Now.ToLongDateString() + " "
                    + DateTime.Now.ToLongTimeString() + "\r\n"
                    + e.ToString() + "\r\n"
                    + "\r\n");
            }
            StdErrLine(e.Message);
        }

    }
}
